package com.websocket.otp;

public class SnowflakeIdGenerator {



    /**
     * 初始时间戳，可以根据业务需求更改时间戳
     */
    private final long twepoch = 1606780506065L;


    /**
     * 机器ID所占位数，长度为5位
     */
    private final long workerIdBits = 5L;

    /**
     * 数据标识ID所占位数，长度位5位
     */
    private final long datacenterIdBits = 5L;

    /**
     * 支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据标识id，结果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 12L;

    /**
     * 工作机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long dataCenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 序列号最大值; 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 工作机器ID(0~31)，2进制5位  32位减掉1位 31个
     */
    private volatile long workerId;

    /**
     * 数据中心ID(0~31)，2进制5位  32位减掉1位 31个
     */
    private volatile long datacenterId;

    /**
     * 毫秒内序列(0~4095)，2进制12位 4096 - 1 = 4095个
     */
    private volatile long sequence = 0L;

    /**
     * 上次时间戳，初始值为负数
     */
    private volatile long lastTimestamp = -1L;


    // ==============================Constructors=====================================

    /**
     * 有参构造
     * @param workerId 工作机器ID(0~31)
     * @param datacenterId 数据中心ID(0~31)
     * @param sequence 毫秒内序列(0~4095)
     */
    public SnowflakeIdGenerator(long workerId, long datacenterId, long sequence){
        // sanity check for workerId
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
        }

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    private static class SnowflakeIdInstance {
        private static final SnowflakeIdGenerator singleton = new SnowflakeIdGenerator(1,1,564);
    }

    //提供一个静态的公有方法，直接返回SingletonInstance.singleton
    public static SnowflakeIdGenerator getInstance() {
        return SnowflakeIdInstance.singleton;
    }

    // ==============================Methods==========================================

    /**
     * 获得下一个ID (该方法是线程安全的)
     *  如果一个线程反复获取Synchronized锁，那么synchronized锁将变成偏向锁。
     * @return 生成的ID
     */
    public synchronized long nextId() {
        // 获取当前时间的时间戳，单位（毫秒）
        long timestamp = timeGen();

        // 获取当前时间戳如果小于上次时间戳，则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            System.err.printf("当前时间戳不能小于上次时间戳，上次时间戳为： %d.", lastTimestamp);
            throw new RuntimeException(String.format("当前时间戳不能小于上次时间戳，生成ID失败. 时间戳差值： %d milliseconds",
                    lastTimestamp - timestamp));
        }

        // 获取当前时间戳如果等于上次时间戳（同一毫秒内），则在序列号加一；否则序列号赋值为0，从0开始。
        if (lastTimestamp == timestamp) {
            /* 逻辑：意思是说一个毫秒内最多只能有4096个数字，无论你传递多少进来，
                    这个位运算保证始终就是在4096这个范围内，避免你自己传递个sequence超过了4096这个范围 */
            // sequence：毫秒内序列(0~4095);  sequenceMask: 序列号最大值;
            sequence = (sequence + 1) & sequenceMask;
            /* 逻辑：当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID */
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        // 将上次时间戳值刷新（逻辑：记录一下最近一次生成id的时间戳，单位是毫秒）
        lastTimestamp = timestamp;

        /* 核心逻辑：生成一个64bit的id；
                  先将当前时间戳左移，放到41 bit那儿；
                  将机房id左移放到5 bit那儿；
                  将机器id左移放到5 bit那儿；
                  将序号放最后12 bit
                  最后拼接起来成一个64 bit的二进制数字，转换成10进制就是个long型 */
        /*
         * 返回结果：
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳，再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符，例如：x | y，只有当x，y不为0的时候结果才为0，其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义，其它位上都是0，所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    /**
     * 上次时间戳与当前时间戳进行比较
     * 逻辑：当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
     * @param lastTimestamp 上次时间戳
     * @return 若当前时间戳小于等于上次时间戳（时间回拨了），则返回最新当前时间戳； 否则，返回当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 获取系统时间戳
     * @return 当前时间的时间戳 14位
     */
    private long timeGen(){
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            System.out.println("雪花算法生成第【"+(i+1)+"】个ID:"+ SnowflakeIdGenerator.getInstance().nextId());
        }

    }

}
